package com.core.db;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Enumeration;
import java.util.Vector;

class ConnPool
{
	/**
	 * 数据库驱动
	 */
	private String driver = "";
	/**
	 * 数据 URL
	 */
	private String url = "";
	/**
	 * 数据库用户名
	 */
	private String username = "";
	/**
	 * 数据库用户密码
	 */
	private String password = "";
	/**
	 * 测试连接是否可用的测试表名，默认没有测试表
	 */
	private String testTable = "";

	/**
	 * 连接池的初始大小
	 */
	private int initConnSize = 10;
	/**
	 * 连接池自动增加的大小
	 */
	private int incrementalConnSize = 5;
	/**
	 * 连接池最大的大小
	 */
	private int maxConnSize = 50;

	/**
	 * 存放连接池中数据库连接的向量 , 初始时为 null
	 */
	private Vector< PooledConnection > conns = null; // 存放连接池中数据库连接的向量 , 初始时为
														// null

	private ConnPool()
	{

	}

	public synchronized void createPool() throws Exception
	{
		// 确保连接池没有创建 如果连接池己经创建了，保存连接的向量 conns 不会为空
		if ( conns != null )
		{
			return;
		}
		// 实例化 JDBC Driver 中指定的驱动类实例
		Driver driver = ( Driver ) ( Class.forName( this.driver ).newInstance() );
		DriverManager.registerDriver( driver ); // 注册 JDBC 驱动程序
		// 创建保存连接的向量 , 初始时有 0 个元素
		conns = new Vector< PooledConnection >();
		// 根据 initConnSize 中设置的值，创建连接。
		createConns( this.initConnSize );
		// System.out.println(" 数据库连接池创建成功！ ");
	}

	/**
	 * 创建由 numconns 指定数目的数据库连接 , 并把这些连接 放入 conns 向量中
	 * 
	 * @param numconns
	 *            要创建的数据库连接的数目
	 */

	private void createConns( int numconns ) throws SQLException
	{
		// 循环创建指定数目的数据库连接
		for ( int x = 0; x < numconns; x++ )
		{
			// 是否连接池中的数据库连接的数量己经达到最大？最大值由类成员 maxconns
			// 指出，如果 maxconns 为 0 或负数，表示连接数量没有限制。
			// 如果连接数己经达到最大，即退出。
			if ( this.maxConnSize > 0 && this.conns.size() >= this.maxConnSize )
			{
				break;
			}
			// add a new PooledConnection object to conns vector
			// 增加一个连接到连接池中（向量 conns 中）
			try
			{
				this.conns.addElement( newConnection() );
			}
			catch ( SQLException e )
			{
				System.out.println( " 创建数据库连接失败！ " + e.getMessage() );
				throw new SQLException();
			}
		}
	}

	/**
	 * 通过调用 getFreeConnection() 函数返回一个可用的数据库连接 , 如果当前没有可用的数据库连接，并且更多的数据库连接不能创
	 * 建（如连接池大小的限制），此函数等待一会再尝试获取。
	 * 
	 * @return 返回一个可用的数据库连接对象
	 */

	public synchronized Connection getConnection() throws SQLException
	{
		// 确保连接池己被创建
		if ( conns == null )
		{
			return null; // 连接池还没创建，则返回 null
		}
		Connection conn = getFreeConnection(); // 获得一个可用的数据库连接
		// 如果目前没有可以使用的连接，即所有的连接都在使用中
		int times = 3;
		while ( conn == null && ( --times ) > 0 )
		{
			// 等一会再试
			// System.out.println("Wait");
			wait( 250 );
			conn = getFreeConnection(); // 重新再试，直到获得可用的连接，如果
			// getFreeConnection() 返回的为 null
			// 则表明创建一批连接后也不可获得可用连接
		}
		// 等待了一会仍没有获取到空闲连接新建一个连接
		if ( conn == null )
		{
			PooledConnection pConn = newConnection();
			if ( testConnection( pConn ) )
			{
				conn = pConn.getConnection();
			}
		}
		conn.setAutoCommit( true );
		return conn;// 返回获得的可用的连接
	}

	/**
	 * 刷新连接池中所有的连接对象
	 * 
	 */
	public synchronized void refreshconns() throws SQLException
	{
		// 确保连接池己创新存在
		if ( conns == null )
		{
			System.out.println( " 连接池不存在，无法刷新 !" );
			return;
		}
		PooledConnection pConn = null;
		Enumeration< PooledConnection > enumerate = conns.elements();
		while ( enumerate.hasMoreElements() )
		{
			// 获得一个连接对象
			pConn = enumerate.nextElement();
			// 如果对象忙则等 5 秒 ,5 秒后直接刷新
			if ( pConn.isBusy() )
			{
				wait( 5000 ); // 等 5 秒
			}
			// 关闭此连接，用一个新的连接代替它。
			closeConnection( pConn );
			pConn = newConnection();
		}
	}

	/**
	 * 关闭连接池中所有的连接，并清空连接池。
	 */

	public synchronized void closeConnectionPool() throws SQLException
	{
		// 确保连接池存在，如果不存在，返回
		if ( conns == null )
		{
			System.out.println( " 连接池不存在，无法关闭 !" );
			return;
		}
		PooledConnection pConn = null;
		Enumeration< PooledConnection > enumerate = conns.elements();
		while ( enumerate.hasMoreElements() )
		{
			pConn = enumerate.nextElement();
			// 如果忙，等 5 秒
			if ( pConn.isBusy() )
			{
				wait( 5000 ); // 等 5 秒
			}
			// 5 秒后直接关闭它
			closeConnection( pConn );
			// 从连接池向量中删除它
			conns.removeElement( pConn );
		}
		// 置连接池为空
		conns = null;
	}

	/**
	 * 创建一个新的数据库连接并返回它
	 * 
	 * @return 返回一个新创建的数据库连接
	 */
	private PooledConnection newConnection() throws SQLException
	{
		// 创建一个数据库连接
		Connection conn = DriverManager.getConnection( url, username, password );
		ClassLoader loader = ConnPool.class.getClassLoader();
		Class< ? >[] inters = conn.getClass().getInterfaces();
		PooledConnection pConn = new PooledConnection();
		Connection proxy = ( Connection ) Proxy.newProxyInstance( loader, inters,
						new IHandler( pConn ) );
		// 如果这是第一次创建数据库连接，即检查数据库，获得此数据库允许支持的最大客户连接数目
		// conns.size()==0 表示目前没有连接己被创建
		if ( conns.size() == 0 )
		{
			DatabaseMetaData metaData = conn.getMetaData();
			int driverMaxconns = metaData.getMaxConnections();
			// 数据库返回的 driverMaxconns 若为 0 ，表示此数据库没有最大
			// 连接限制，或数据库的最大连接限制不知道
			// driverMaxconns 为返回的一个整数，表示此数据库允许客户连接的数目
			// 如果连接池中设置的最大连接数量大于数据库允许的连接数目 , 则置连接池的最大
			// 连接数目为数据库允许的最大数目
			if ( driverMaxconns > 0 && this.maxConnSize > driverMaxconns )
			{
				this.maxConnSize = driverMaxconns;
			}
		}
		pConn.setTarget( conn );
		pConn.setConnection( proxy );
		pConn.setBusy( false );
		return pConn; // 返回创建的新的数据库连接
	}

	/**
	 * 本函数从连接池向量 conns 中返回一个可用的的数据库连接，如果 当前没有可用的数据库连接，本函数则根据 incrementalconns 设置
	 * 的值创建几个数据库连接，并放入连接池中。 如果创建后，所有的连接仍都在使用中，则返回 null
	 * 
	 * @return 返回一个可用的数据库连接
	 */
	private Connection getFreeConnection() throws SQLException
	{
		// 从连接池中获得一个可用的数据库连接
		Connection conn = findFreeConnection();
		if ( conn == null )
		{
			// 如果目前连接池中没有可用的连接
			// 创建一些连接
			createConns( incrementalConnSize );
			// 重新从池中查找是否有可用连接
			conn = findFreeConnection();
			if ( conn == null )
			{
				// 如果创建连接后仍获得不到可用的连接，则返回 null
				return null;
			}
		}
		return conn;
	}

	/**
	 * 查找连接池中所有的连接，查找一个可用的数据库连接， 如果没有可用的连接，返回 null
	 * 
	 * @return 返回一个可用的数据库连接
	 */

	private Connection findFreeConnection() throws SQLException
	{
		Connection conn = null;
		PooledConnection pConn = null;
		// 获得连接池向量中所有的对象
		Enumeration< PooledConnection > enumerate = conns.elements();
		// 遍历所有的对象，看是否有可用的连接
		while ( enumerate.hasMoreElements() )
		{
			pConn = enumerate.nextElement();
			if ( !pConn.isBusy() )
			{
				// 如果此对象不忙，则获得它的数据库连接并把它设为忙
				conn = pConn.getConnection();
				pConn.setBusy( true );
				// 测试此连接是否可用
				if ( !testConnection( pConn ) )
				{
					// 如果此连接不可再用了，则创建一个新的连接，
					// 并替换此不可用的连接对象，如果创建失败，返回 null
					try
					{
						pConn = newConnection();
					}
					catch ( SQLException e )
					{
						System.out.println( " 创建数据库连接失败！ " + e.getMessage() );
						conn.close();
						return null;
					}
					conn = pConn.getConnection();
				}
				break; // 己经找到一个可用的连接，退出
			}
		}
		return conn;// 返回找到到的可用连接
	}

	/**
	 * 测试一个连接是否可用，如果不可用，关掉它并返回 false 否则可用返回 true
	 * 
	 * @param conn需要测试的数据库连接
	 * @return 返回 true 表示此连接可用， false 表示不可用
	 */

	private boolean testConnection( PooledConnection pConn )
	{
		try
		{
			// 判断测试表是否存在
			if ( testTable.equals( "" ) )
			{
				// 如果测试表为空，试着使用此连接的 setAutoCommit() 方法
				// 来判断连接否可用（此方法只在部分数据库可用，如果不可用 ,
				// 抛出异常）。注意：使用测试表的方法更可靠
				pConn.getConnection().setAutoCommit( true );
			}
			else
			{// 有测试表的时候使用测试表测试
				// check if this connection is valid
				Statement stmt = pConn.getConnection().createStatement();
				stmt.execute( "select count(*) from " + testTable );
			}
		}
		catch ( SQLException e )
		{
			// 上面抛出异常，此连接己不可用，关闭它，并返回 false;
			closeConnection( pConn );
			return false;
		}
		// 连接可用，返回 true
		return true;
	}

	/**
	 * 关闭一个数据库连接
	 * 
	 * @param 需要关闭的数据库连接
	 */

	private void closeConnection( PooledConnection pConn )
	{
		try
		{
			pConn.setNeedRelease( true );
			pConn.getConnection().close();
		}
		catch ( SQLException e )
		{
			System.out.println( " 关闭数据库连接出错： " + e.getMessage() );
		}
	}

	/**
	 * 使程序等待给定的毫秒数
	 * 
	 * @param 给定的毫秒数
	 */

	private void wait( int mSeconds )
	{
		try
		{
			Thread.sleep( mSeconds );
		}
		catch ( InterruptedException e )
		{
		}
	}

	private class IHandler implements InvocationHandler
	{
		private PooledConnection pConn;

		public IHandler( PooledConnection pConn )
		{
			this.pConn = pConn;
		}

		@Override
		public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable
		{
			if ( pConn != null && "close".equals( method.getName() ) && !pConn.isNeedRelease )
			{
				pConn.setBusy( false );
				return null;
			}

			return method.invoke( pConn.getTarget(), args );
		}
	}

	/**
	 * 
	 * 内部使用的用于保存连接池中连接对象的类 此类中有两个成员，一个是数据库的连接，另一个是指示此连接是否 正在使用的标志。
	 */
	private class PooledConnection
	{
		private Connection target = null;// 数据库连接
		private Connection porxy = null;
		private boolean busy = false; // 此连接是否正在使用的标志，默认没有正在使用
		private boolean isNeedRelease = false;// 此连接是否需要释放;

		// 构造函数，根据一个 Connection 构告一个 PooledConnection 对象
		public PooledConnection()
		{
		}

		public PooledConnection( Connection conn )
		{
			this.porxy = conn;
		}

		public Connection getConnection()
		{
			return this.porxy;
		}

		public void setConnection( Connection conn )
		{
			setNeedRelease( false );
			this.porxy = conn;
		}

		public Connection getTarget()
		{
			return target;
		}

		public void setTarget( Connection target )
		{
			this.target = target;
		}

		// 获得对象连接是否忙
		public boolean isBusy()
		{
			return busy;
		}

		// 设置对象的连接正在忙
		public void setBusy( boolean busy )
		{
			this.busy = busy;
		}

		public boolean isNeedRelease()
		{
			return isNeedRelease;
		}

		public void setNeedRelease( boolean isNeedRelease )
		{
			this.isNeedRelease = isNeedRelease;
		}
	}

	public static class Builder
	{
		private String driver = "";
		private String url = "";
		private String username = "";
		private String password = "";
		private String testTable = "";
		private int initConnSize = 10;
		private int incrementalConnSize = 5;
		private int maxConnSize = 50;

		public void setDriver( String driver )
		{
			this.driver = driver;
		}

		public void setUrl( String url )
		{
			this.url = url;
		}

		public void setUsername( String username )
		{
			this.username = username;
		}

		public void setPassword( String password )
		{
			this.password = password;
		}

		public void setTestTable( String testTable )
		{
			this.testTable = testTable;
		}

		public void setInitConnSize( int initConnSize )
		{
			this.initConnSize = initConnSize;
		}

		public void setIncrementalConnSize( int incrementalConnSize )
		{
			this.incrementalConnSize = incrementalConnSize;
		}

		public void setMaxConnSize( int maxConnSize )
		{
			this.maxConnSize = maxConnSize;
		}

		public ConnPool build() throws IllegalArgumentException
		{
			try
			{
				Class.forName( driver );
			}
			catch ( ClassNotFoundException e )
			{
				throw new IllegalArgumentException( "jdbc driver not found" );
			}

			if ( url == null || url.length() < 0 )
			{
				throw new IllegalArgumentException( "url can't be empty" );
			}
			if ( username == null || username.length() < 0 )
			{
				throw new IllegalArgumentException( "username can't be empty" );
			}
			if ( password == null || password.length() < 0 )
			{
				throw new IllegalArgumentException( "password can't be empty" );
			}

			ConnPool pool = new ConnPool();
			pool.driver = driver;
			pool.url = url;
			pool.username = username;
			pool.password = password;
			pool.testTable = testTable;
			pool.initConnSize = initConnSize < 0 ? 10 : initConnSize;
			pool.maxConnSize = maxConnSize < 0 ? 50 : maxConnSize;
			pool.incrementalConnSize = incrementalConnSize < 0 ? 5 : incrementalConnSize;
			return pool;
		}
	}
}
